﻿//-----------------------------------------------------------------------
// <copyright file="SPSDemos.cs" company="Danny Jessee">
//     This code is released under the Microsoft Public License (Ms-PL).
//     For more information, see http://facebookwebparts.codeplex.com/license.
// </copyright>
//-----------------------------------------------------------------------
namespace FacebookWebParts
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.IO;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using System.Web.UI.WebControls.WebParts;
    using Facebook;
    using Microsoft.IdentityModel.Claims;
    using Microsoft.Office.Server;
    using Microsoft.Office.Server.UserProfiles;
    using Microsoft.SharePoint;
    using Microsoft.SharePoint.WebControls;
    
    /// <summary>
    /// The SPSDemos web part contains functionality used in demonstrations of the integration
    /// between Facebook, Azure ACS, and SharePoint 2010.
    /// </summary>
    [ToolboxItemAttribute(false)]
    public class SPSDemos : WebPart
    {
        /// <summary>
        /// Label used for displaying output and error information.
        /// </summary>
        private Label lblOutput = new Label();

        /// <summary>
        /// Button used for the "Create SPUser - Fix User Name" demo.
        /// </summary>
        private Button btnFixUserName = new Button();

        /// <summary>
        /// Button used for the "Add Profile Information to Contacts List" demo.
        /// </summary>
        private Button btnContactList = new Button();

        /// <summary>
        /// Button used for the "Populate Calendar List with Friends' Birthdays" demo.
        /// </summary>
        private Button btnFriendsBdayCalendar = new Button();

        /// <summary>
        /// Button used for the "Do you guys know how to post videos to Facebook?" demo.
        /// </summary>
        private Button btnVideo = new Button();

        /// <summary>
        /// Button used for the "Populate User Profile" demo.
        /// </summary>
        private Button btnUserProfile = new Button();

        /// <summary>
        /// String used to hold the Facebook Graph API AccessToken.
        /// </summary>
        private string accessToken = string.Empty;

        /// <summary>
        /// String used to hold the Facebook user's given name.
        /// </summary>
        private string givenName = string.Empty;

        /// <summary>
        /// String used to hold the Facebook user's name identifier.
        /// </summary>
        private string nameIdentifier = string.Empty;

        /// <summary>
        /// String used to hold the Facebook user's email address.
        /// </summary>
        private string email = string.Empty;

        /// <summary>
        /// Used to iterate through the current user's claims.
        /// </summary>
        private IClaimsIdentity claimsIdentity;

        /// <summary>
        /// String value containing the Facebook AccessToken claim URI.
        /// </summary>
        private string accessTokenClaimUri = string.Empty;
        
        /// <summary>
        /// String value containing the Facebook email address claim URI.
        /// </summary>
        private string emailClaimUri = string.Empty;

        /// <summary>
        /// String value containing the Facebook given name claim URI.
        /// </summary>
        private string givenNameClaimUri = string.Empty;

        /// <summary>
        /// Gets or sets the string value containing the URI of the Facebook Graph API AccessToken claim.
        /// By default, this URI is http://www.facebook.com/claims/AccessToken.
        /// </summary>
        [WebBrowsable(true), Category("Custom Properties")]
        [WebDisplayName("Facebook Access Token Claim URI")]
        [Personalizable(PersonalizationScope.Shared)]
        public string FacebookAccessTokenClaimUri
        {
            get
            {
                if (string.IsNullOrEmpty(this.accessTokenClaimUri))
                {
                    this.accessTokenClaimUri = "http://www.facebook.com/claims/AccessToken";
                }

                return this.accessTokenClaimUri;
            }

            set
            {
                this.accessTokenClaimUri = value;
            }
        }

        /// <summary>
        /// Gets or sets the string value containing the URI of the email address claim.
        /// By default, this URI is http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress.
        /// </summary>
        [WebBrowsable(true), Category("Custom Properties")]
        [WebDisplayName("Email Address Claim URI")]
        [Personalizable(PersonalizationScope.Shared)]
        public string EmailAddressClaimUri
        {
            get
            {
                if (string.IsNullOrEmpty(this.emailClaimUri))
                {
                    this.emailClaimUri = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
                }

                return this.emailClaimUri;
            }

            set
            {
                this.emailClaimUri = value;
            }
        }

        /// <summary>
        /// Gets or sets the string value containing the URI of the given name claim.
        /// By default, this URI is http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname.
        /// </summary>
        [WebBrowsable(true), Category("Custom Properties")]
        [WebDisplayName("Given Name Claim URI")]
        [Personalizable(PersonalizationScope.Shared)]
        public string GivenNameClaimUri
        {
            get
            {
                if (string.IsNullOrEmpty(this.givenNameClaimUri))
                {
                    this.givenNameClaimUri = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname";
                }

                return this.givenNameClaimUri;
            }

            set
            {
                this.givenNameClaimUri = value;
            }
        }

        /// <summary>
        /// Gets or sets the string value containing the Facebook Graph API AccessToken.
        /// </summary>
        private string FacebookAccessToken
        {
            get
            {
                if (string.IsNullOrEmpty(this.accessToken))
                {
                    try
                    {
                        this.accessToken = (from c in this.claimsIdentity.Claims
                                            where c.ClaimType.Equals(this.FacebookAccessTokenClaimUri)
                                            select c.Value).FirstOrDefault();
                    }
                    catch (Exception)
                    {
                        this.accessToken = string.Empty;
                    }
                }

                return this.accessToken;
            }

            set
            {
                this.accessToken = value;
            }
        }

        /// <summary>
        /// Gets or sets the string value containing the user's email address.
        /// </summary>
        private string EmailAddress
        {
            get
            {
                if (string.IsNullOrEmpty(this.email))
                {
                    try
                    {
                        this.email = (from c in this.claimsIdentity.Claims
                                      where c.ClaimType.Equals(this.EmailAddressClaimUri)
                                      select c.Value).FirstOrDefault();
                    }
                    catch (Exception)
                    {
                        this.email = string.Empty;
                    }
                }

                return this.email;
            }

            set
            {
                this.email = value;
            }
        }

        /// <summary>
        /// Gets or sets the string value containing the user's given name.
        /// We will use this claim to set the proper display name for the user.
        /// The Claims-encoded format of the user name looks like i:05.t|azure acs|djessee@gmail.com
        /// </summary>
        private string GivenName
        {
            get
            {
                if (string.IsNullOrEmpty(this.givenName))
                {
                    try
                    {
                        // The Name property of the IClaimsIdentity has everything but the leading "i:"
                        this.nameIdentifier = "i:" + this.claimsIdentity.Name;

                        this.givenName = (from c in this.claimsIdentity.Claims
                                          where c.ClaimType.Equals(this.GivenNameClaimUri)
                                          select c.Value).FirstOrDefault();
                    }
                    catch (Exception)
                    {
                        this.givenName = string.Empty;
                    }
                }

                return this.givenName;
            }

            set
            {
                this.givenName = value;
            }
        }

        /// <summary>
        /// Sets the IClaimsPrincipal object based on the current user.
        /// </summary>
        /// <param name="e">Default EventArgs parameter.</param>
        protected override void OnLoad(EventArgs e)
        {
            // Set the IClaimsPrincipal based on the current user
            IClaimsPrincipal claimsPrincipal = Page.User as IClaimsPrincipal;
            this.claimsIdentity = (IClaimsIdentity)claimsPrincipal.Identity;
            
            base.OnLoad(e);
        }

        /// <summary>
        /// Add buttons to the web part's Controls collection and wire up event handlers for button clicks.
        /// </summary>
        protected override void CreateChildControls()
        {
            this.Controls.Add(this.lblOutput);
            this.Controls.Add(new LiteralControl("<br/><br/>"));

            this.btnFixUserName.Text = "Create SPUser - Fix User Name";
            this.btnFixUserName.Click += new EventHandler(this.BtnFixUserName_Click);
            this.Controls.Add(this.btnFixUserName);
            this.Controls.Add(new LiteralControl("<br/><br/>"));

            this.btnContactList.Text = "Add Profile Information to Contacts List";
            this.btnContactList.Click += new EventHandler(this.BtnContactList_Click);
            this.Controls.Add(this.btnContactList);
            this.Controls.Add(new LiteralControl("<br/><br/>"));

            this.btnFriendsBdayCalendar.Text = "Populate Calendar List with Friends' Birthdays";
            this.btnFriendsBdayCalendar.Click += new EventHandler(this.BtnFriendsBdayCalendar_Click);
            this.Controls.Add(this.btnFriendsBdayCalendar);
            this.Controls.Add(new LiteralControl("<br/><br/>"));

            this.btnVideo.Text = "Do you guys know how to post videos to Facebook?";
            this.btnVideo.Click += new EventHandler(this.BtnVideo_Click);
            this.Controls.Add(this.btnVideo);
            this.Controls.Add(new LiteralControl("<br/><br/>"));

            this.btnUserProfile.Text = "Populate User Profile";
            this.btnUserProfile.Click += new EventHandler(this.BtnUserProfile_Click);
            this.Controls.Add(this.btnUserProfile);
            this.Controls.Add(new LiteralControl("<br/><br/>"));
        }

        /// <summary>
        /// Populate the Facebook user's SharePoint user profile.
        /// </summary>
        /// <param name="sender">Default object parameter.</param>
        /// <param name="e">Default EventArgs parameter.</param>
        private void BtnUserProfile_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(this.FacebookAccessTokenClaimUri))
            {
                var client = new Facebook.FacebookClient(this.FacebookAccessToken);
                var me = (IDictionary<string, object>)client.Get("me");

                Guid siteId = SPContext.Current.Site.ID;
                Guid webId = SPContext.Current.Web.ID;
                using (SPSite site = new SPSite(siteId, SPUserToken.SystemAccount))
                {
                    try
                    {
                        SPServiceContext sc = SPServiceContext.GetContext(site);
                        UserProfileManager userProfileMangager = new UserProfileManager(sc);
                        SPUser currentUser = SPContext.Current.Web.CurrentUser;
                        if (currentUser != null)
                        {
                            UserProfile profile = userProfileMangager.GetUserProfile(true);
                            profile.DisplayName = this.GivenName;
                            profile[PropertyConstants.Birthday].Value = (string)me["birthday"];
                            profile[PropertyConstants.FirstName].Value = (string)me["first_name"];
                            profile[PropertyConstants.LastName].Value = (string)me["last_name"];
                            profile[PropertyConstants.PreferredName].Value = this.GivenName;
                            profile[PropertyConstants.AboutMe].Value = (string)me["bio"];
                                    
                            JsonArray work = me["work"] as JsonArray;
                            
                            // Most recent/current employer stored in work[0]
                            JsonObject company = work[0] as JsonObject;
                            if (company.ContainsKey("position"))
                            {
                                JsonObject position = company["position"] as JsonObject;
                                profile[PropertyConstants.JobTitle].Value = (string)position["name"];
                                profile[PropertyConstants.Title].Value = (string)position["name"];
                            }
                                    
                            JsonObject location = me["location"] as JsonObject;
                            string cityState = (string)location["name"];
                            profile[PropertyConstants.Location].Value = cityState;

                            profile[PropertyConstants.PictureUrl].Value = "https://graph.facebook.com/" + (string)me["id"] + "/picture?type=large";
                                    
                            string url = (string)me["website"];
                            url = url.StartsWith("http") ? url : "http://" + url;
                            profile[PropertyConstants.WebSite].Value = url;

                            profile[PropertyConstants.StatusNotes].Value = "I updated my user profile on " + DateTime.Now.ToLongDateString() + " at " + DateTime.Now.ToLongTimeString();

                            profile.Commit();
                        }

                        this.lblOutput.Text = "<font color=green><b>User profile successfully updated.</b></font>";
                    }
                    catch (Exception ex)
                    {
                        this.lblOutput.Text = "<font color=red><b>" + ex.Message + "<br/>" + ex.StackTrace + "</font>";
                    }                            
                }
            }
        }

        /// <summary>
        /// Posts a video (from the file system) to the Facebook user's feed.
        /// </summary>
        /// <param name="sender">Default object parameter.</param>
        /// <param name="e">Default EventArgs parameter.</param>
        private void BtnVideo_Click(object sender, EventArgs e)
        {
            try
            {
                if (!string.IsNullOrEmpty(this.FacebookAccessTokenClaimUri))
                {
                    var client = new Facebook.FacebookClient(this.FacebookAccessToken);
                    Dictionary<string, object> dict = new Dictionary<string, object> 
                    { 
                        { "title", "Do you guys know how to post videos to Facebook?" },
                        { "description", "Now we do...from SharePoint!" },
                        { "vid1", new FacebookMediaObject { ContentType = "video/x-flv", FileName = "facebook.flv" }.SetValue(File.ReadAllBytes(@"C:\facebook.flv")) }
                    };
                    client.PostAsync("me/videos", dict);

                    this.lblOutput.Text = "<font color=green><b>We know how to post videos to Facebook!</b></font>";
                }
            }
            catch (Exception ex)
            {
                this.lblOutput.Text = "<font color=red><b>" + ex.Message + "<br/>" + ex.StackTrace + "</font>";
            }
        }

        /// <summary>
        /// Populate a SharePoint calendar list with the Facebook user's friends' birthdays
        /// </summary>
        /// <param name="sender">Default object parameter.</param>
        /// <param name="e">Default EventArgs parameter.</param>
        private void BtnFriendsBdayCalendar_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(this.FacebookAccessTokenClaimUri))
            {
                int numEntries = 0;
                var client = new Facebook.FacebookClient(this.FacebookAccessToken);
                var me = (IDictionary<string, object>)client.Get("me/friends?fields=name,birthday");
                JsonArray friendData = me["data"] as JsonArray;
                foreach (JsonObject friend in friendData)
                {
                    if (friend.ContainsKey("birthday"))
                    {
                        int birthMonth = 0;
                        int birthDate = 0;
                        string name = string.Empty;
                        try
                        {
                            // Not all users share their birthdays, even with friends
                            name = (string)friend["name"];
                            
                            // Some users share MM/DD birthday, others share MM/DD/YYYY
                            // We only care about MM/DD for our purposes, and Facebook pads with leading zeros
                            string birthday = (string)friend["birthday"];
                            birthMonth = int.Parse(birthday.Substring(0, 2));
                            birthDate = int.Parse(birthday.Substring(3, 2));
                            DateTime birthdayDate = DateTime.MinValue;
                            try
                            {
                                birthdayDate = new DateTime(DateTime.Now.Year, birthMonth, birthDate);
                            }
                            catch
                            {
                                // Leap year birthday won't be a problem in 2012!
                            }

                            // Add to calendar
                            Guid siteId = SPContext.Current.Site.ID;
                            Guid webId = SPContext.Current.Web.ID;
                            using (SPSite site = new SPSite(siteId, SPUserToken.SystemAccount))
                            {
                                using (SPWeb web = site.OpenWeb(webId))
                                {
                                    web.AllowUnsafeUpdates = true;
                                    SPList lstCalendar = web.Lists["Calendar"];
                                    SPListItem birthdayItem = lstCalendar.Items.Add();
                                    birthdayItem["Title"] = name + (name.EndsWith("s") ? "' birthday" : "'s birthday");
                                    birthdayItem["EventDate"] = birthdayDate;
                                    birthdayItem["EndDate"] = birthdayDate.AddYears(3);
                                    birthdayItem[SPBuiltInFieldId.Duration] = 60 * 60 * 24; // All day: 60 seconds * 60 minutes * 24 hours
                                    birthdayItem[SPBuiltInFieldId.EventType] = 1;
                                    birthdayItem[SPBuiltInFieldId.fRecurrence] = true;
                                    birthdayItem[SPBuiltInFieldId.fAllDayEvent] = true;
                                    
                                    // Calendar recurrence CAML reference: http://msdn.microsoft.com/en-us/library/ms434156.aspx
                                    string recurrence = "<recurrence><rule><firstDayOfWeek>su</firstDayOfWeek>" +
                                                        "<repeat><yearly yearFrequency='1' month='" + birthMonth.ToString() + "' day='" + birthDate.ToString() + "' /></repeat>" +
                                                        "<windowEnd>2014-01-01T00:00:00Z</windowEnd></rule></recurrence>";

                                    birthdayItem["RecurrenceData"] = recurrence;
                                    birthdayItem.SystemUpdate();
                                    ++numEntries;
                                }
                            }
                        }
                        catch (Exception ex)
                        {
                            this.lblOutput.Text = "<font color=red><b>" + ex.Message + "<br/>" + ex.StackTrace + "</font>";
                        }
                    }
                }

                this.lblOutput.Text = string.Format("<font color=green><b>{0} friends' birthday entries successfully added to Calendar list.</b></font>", numEntries);
            }
        }

        /// <summary>
        /// Populate a specialized SharePoint Contacts list with profile information from the current Facebook user.
        /// </summary>
        /// <param name="sender">Default object parameter.</param>
        /// <param name="e">Default EventArgs parameter.</param>
        private void BtnContactList_Click(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(this.FacebookAccessTokenClaimUri))
            {
                var client = new Facebook.FacebookClient(this.FacebookAccessToken);
                var me = (IDictionary<string, object>)client.Get("me");
                
                Guid siteId = SPContext.Current.Site.ID;
                Guid webId = SPContext.Current.Web.ID;
                using (SPSite site = new SPSite(siteId, SPUserToken.SystemAccount))
                {
                    using (SPWeb web = site.OpenWeb(webId))
                    {
                        try
                        {
                            web.AllowUnsafeUpdates = true;
                            SPList lstContacts = web.Lists["Contacts"];
                            SPListItem item = lstContacts.Items.Add();
                            item["First Name"] = (string)me["first_name"];
                            item["Last Name"] = (string)me["last_name"];
                            item["Full Name"] = (string)me["name"];
                            item["E-mail Address"] = (string)me["email"];
                            if (me.ContainsKey("hometown"))
                            {
                                JsonObject hometown = me["hometown"] as JsonObject;
                                item["Hometown"] = (string)hometown["name"];
                            }

                            if (me.ContainsKey("birthday"))
                            {
                                item["Birthday"] = (string)me["birthday"];
                            }

                            if (me.ContainsKey("gender"))
                            {
                                item["Gender"] = (string)me["gender"];
                            }

                            if (me.ContainsKey("website"))
                            {
                                SPFieldUrlValue fldWebPage = new SPFieldUrlValue();
                                string url = (string)me["website"];
                                fldWebPage.Url = url.StartsWith("http") ? url : "http://" + url;
                                fldWebPage.Description = url;
                                item["Web Page"] = fldWebPage;
                            }

                            if (me.ContainsKey("location"))
                            {
                                JsonObject location = me["location"] as JsonObject;
                                string cityState = (string)location["name"];
                                item["City"] = cityState.Substring(0, cityState.IndexOf(','));
                                item["State/Province"] = cityState.Substring(cityState.IndexOf(',') + 1);
                            }

                            if (me.ContainsKey("work"))
                            {
                                JsonArray work = me["work"] as JsonArray;
                                
                                // Most recent/current employer stored in work[0]
                                JsonObject company = work[0] as JsonObject;
                                if (company.ContainsKey("employer") && company.ContainsKey("position"))
                                {
                                    JsonObject employer = company["employer"] as JsonObject;
                                    JsonObject position = company["position"] as JsonObject;
                                    item["Company"] = (string)employer["name"];
                                    item["Job Title"] = (string)position["name"];
                                }
                            }

                            item.SystemUpdate();

                            this.lblOutput.Text = "<font color=green><b>Entry successfully added to Contacts list.</b></font>";
                        }
                        catch (Exception ex)
                        {
                            this.lblOutput.Text = "<font color=red><b>" + ex.Message + "<br/>" + ex.StackTrace + "</font>";
                        }
                    }
                }
            }
            else
            {
                this.Controls.Add(new LiteralControl("<font size=2>Please ensure the Facebook AccessToken claim URI is populated and that you are signed in via Facebook.</font>"));
            }
        }

        /// <summary>
        /// Handles a number of different scenarios for updating the display name of the SPUser object associated
        /// with the current Facebook user to match his/her "friendly" given name.
        /// </summary>
        /// <param name="sender">Default object parameter.</param>
        /// <param name="e">Default EventArgs parameter.</param>
        private void BtnFixUserName_Click(object sender, EventArgs e)
        {
            try
            {
                Guid siteId = SPContext.Current.Site.ID;
                Guid webId = SPContext.Current.Web.ID;
                SPUser currentUser = SPContext.Current.Web.CurrentUser;
                using (SPSite site = new SPSite(siteId, SPUserToken.SystemAccount))
                {
                    using (SPWeb web = site.OpenWeb(webId))
                    {
                        web.AllowUnsafeUpdates = true;
                        if (currentUser == null)
                        {
                            // Create the SPUser and set his/her display name
                            SPUser user = web.EnsureUser(this.nameIdentifier);
                            user.Name = this.GivenName;
                            user.Update();

                            this.lblOutput.Text = "<font color=green><b>SPUser created and display name successfully updated.</b></font>";
                        }

                        // Alternate approach to setting name - sometimes the "i:" preface is not there and the email address is used instead
                        // This could be based on user's Facebook settings? Need to do more investigating here...
                        if (currentUser == null)
                        {
                            if (!string.IsNullOrEmpty(this.claimsIdentity.Name))
                            {
                                currentUser = web.EnsureUser(this.claimsIdentity.Name);

                                if (!string.IsNullOrEmpty(this.GivenName))
                                {
                                    currentUser.Name = this.GivenName;
                                    currentUser.Update();
                                    this.lblOutput.Text = "<font color=green><b>SPUser display name successfully updated.</b></font>";
                                }
                            }
                        }
                        else
                        {
                            string loginName = currentUser.LoginName;
                            if (currentUser.Name.Equals(this.EmailAddress) || currentUser.Name.Equals(loginName))
                            {
                                SPUser newUser = web.AllUsers[loginName];
                                newUser.Name = this.GivenName;
                                newUser.Update();
                                this.lblOutput.Text = "<font color=green><b>Display name successfully updated.</b></font>";
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                this.lblOutput.Text = "<font color=red><b>" + ex.Message + "<br/>" + ex.StackTrace + "</font>";
            }
        }
    }
}